/*
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Modified by Stefan Tillich, University of Bristol
 * - Adapted from TracksFragment.java
 * - Removed Google Analytics
 * - Changed package name
 */

package org.iacr.android.apps.yaea.ui;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.iacr.android.apps.yaea.R;

import android.content.ContentProviderOperation;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;

import android.content.ContentResolver;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpParams;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
//import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.iacr.android.apps.yaea.provider.ScheduleContract;
import org.iacr.android.apps.yaea.provider.ScheduleContract.RumpSession;
import org.iacr.android.apps.yaea.provider.ScheduleContract.SyncColumns;
import org.iacr.android.apps.yaea.ui.phone.RumpSessionActivity;
//import org.iacr.android.apps.yaea.util.AnalyticsUtils;
import org.iacr.android.apps.yaea.util.Lists;
import org.iacr.android.apps.yaea.util.NotifyingAsyncQueryHandler;
import org.iacr.android.apps.yaea.util.ParserUtils;


/**
 * A simple {@link ListFragment} that renders a list of rump session talks.
 */
public class RumpSessionFragment extends ListFragment implements
        NotifyingAsyncQueryHandler.AsyncQueryListener {

	private RumpSessionAdapter mAdapter;
    private NotifyingAsyncQueryHandler mHandler;
                
    private static final int TOTAL_VOTES_ALLOWED = 3;
    public static final long VOTING_START = ParserUtils.parseTime(
    		"2012-04-17T17:00:00.000+01:00");
    public static final long VOTING_END = ParserUtils.parseTime(
            "2012-04-17T23:59:59.000+01:00");
    
    private List<Integer> mVotes;
    private String mUserPseudonym;
    
    private int mCurrentTalkId;
     
    private final String RUMPSESSION_SERVER_URL = "https://www.cs.bris.ac.uk/eurocrypt2012/Android/vote-action.jsp";

    
    public RumpSessionFragment() {
    	mVotes = null;
    	mUserPseudonym = null;
    	mCurrentTalkId = -1;
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final Intent intent = BaseActivity.fragmentArgumentsToIntent(getArguments());
        final Uri rumpSessionUri = intent.getData();

        mAdapter = new RumpSessionAdapter(getActivity());
        
        setListAdapter(mAdapter);

        // Start background query to load rump session talks with vote data
        mHandler = new NotifyingAsyncQueryHandler(getActivity().getContentResolver(), this);
        mHandler.startQuery(rumpSessionUri, RumpSessionAdapter.RumpSessionQuery.PROJECTION, null, null,
                ScheduleContract.RumpSession.DEFAULT_SORT);
        
        setHasOptionsMenu(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_list_with_spinner, null);

        // For some reason, if we omit this, NoSaveStateFrameLayout thinks we are
        // FILL_PARENT / WRAP_CONTENT, making the progress bar stick to the top of the activity.
        root.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
                ViewGroup.LayoutParams.FILL_PARENT));
        return root;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        
    	// If needed, try to get the user pseudonym from the SharedPreferences
    	if (mUserPseudonym == null) {
    		SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
    		mUserPseudonym = sp.getString("user_pseudonym", null);
    	}
        // If the user pseudonym is not set, show the entry prompt
        if (mUserPseudonym == null) {
        	getActivity().showDialog(RumpSessionActivity.USER_PSEUDONYM_DIALOG);
        }
    }
    
    
    // This sort of works (only the "map" menu point is shown), no
    // idea where the "share" menu point is ...
    // Clicking on the "map" menu point will give you the user
    // pseudonym entry prompt.
    
    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    	//menu.add(Menu.NONE, MENU_SET_PSEUDONYM, Menu.NONE, "Set pseudonym");
    	inflater.inflate(R.menu.rump_session_menu_items, menu);
    	//super.onCreateOptionsMenu(menu, inflater);
    }
    
    public boolean onOptionsItemSelected(MenuItem item) {    	
    	
        switch (item.getItemId()) {
            case R.id.menu_pseudonym:
            	getActivity().showDialog(RumpSessionActivity.USER_PSEUDONYM_DIALOG);
                return true;
            
            /*
            case R.id.menu_share:
            	getActivity().showDialog(RumpSessionActivity.USER_PSEUDONYM_DIALOG);
                return true;
            */
        }
        return super.onOptionsItemSelected(item);
    	
        //switch (item.getItemId()) {
    	//case MENU_SET_PSEUDONYM:
    	//	getActivity().showDialog(RumpSessionActivity.USER_PSEUDONYM_DIALOG);
        //    return true;
        //}

        //return false;
    }
    
    

    /** {@inheritDoc} */
    public void onQueryComplete(int token, Object cookie, Cursor cursor) {
        if (getActivity() == null) {
        	
        	// HACK!!!
        	// Try to redo the query in the hope that getActivity will 
        	// succeed when it is complete (this is to prevent the spinner
        	// from hanging at first load)
        	final Intent intent = BaseActivity.fragmentArgumentsToIntent(getArguments());
            final Uri rumpSessionUri = intent.getData();
        	mHandler.startQuery(rumpSessionUri, RumpSessionAdapter.RumpSessionQuery.PROJECTION, null, null,
                    ScheduleContract.RumpSession.DEFAULT_SORT);
        	
            return;
        }

        getActivity().startManagingCursor(cursor);
        mAdapter.setHasAllItem(true);        
        mAdapter.changeCursor(cursor);
        
        // Populate the list of votes (if that has not been done already)
        if (mVotes == null) {
        	mVotes = new ArrayList<Integer>();
        	while (cursor.moveToNext()) {
        		int vote = cursor.getInt(RumpSessionAdapter.RumpSessionQuery.RUMPSESSION_VOTE);
        		int talk_id = cursor.getInt(RumpSessionAdapter.RumpSessionQuery.RUMPSESSION_ORDER);
        		while (vote > 0) {
        			mVotes.add(new Integer(talk_id));
        			vote--;
        		}
        	}
        }
    }
    

    /** {@inheritDoc} */
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        final Cursor cursor = (Cursor) mAdapter.getItem(position);
        final String rumpSessionTalkId;

        if (cursor != null) {
        	rumpSessionTalkId = cursor.getString(RumpSessionAdapter.RumpSessionQuery.RUMPSESSION_ORDER);
        	mCurrentTalkId  = cursor.getInt(RumpSessionAdapter.RumpSessionQuery.RUMPSESSION_ORDER);
        } else {
        	rumpSessionTalkId = ScheduleContract.RumpSession.ALL_RUMPSESSION_TALKS_ID;
        }
        
        final long current_time = System.currentTimeMillis();
            	
    	// Show an appropriate dialog:
        // - Allow voting only in a specific time window
        // - Alerts user if user pseudonym is not yet set
    	// - Add a vote (if total number of votes < TOTAL_VOTES_ALLOWED)
    	// - Remove a vote (if there > 0 votes for that talk)
    	// - Cancel: No vote is added or removed
        // - Alerts user if she has already reached the maximum number of
        //   votes allowed
        if (current_time < VOTING_START) {
        	getActivity().showDialog(RumpSessionActivity.VOTING_NOT_YET_STARTED_ALERT);
        } else if (current_time > VOTING_END) {
            getActivity().showDialog(RumpSessionActivity.VOTING_ENDED_ALERT);
        } else if (mUserPseudonym == null) {
        	getActivity().showDialog(RumpSessionActivity.NO_PSEUDONYM_ALERT);
        } else if (mVotes.size() < TOTAL_VOTES_ALLOWED && numVotesForTalk(mCurrentTalkId) == 0) {
    		getActivity().showDialog(RumpSessionActivity.VOTE_CANCEL_DIALOG);
    	} else if (mVotes.size() >= TOTAL_VOTES_ALLOWED && numVotesForTalk(mCurrentTalkId) > 0) {
    		getActivity().showDialog(RumpSessionActivity.UNVOTE_CANCEL_DIALOG);
    	} else if (mVotes.size() < TOTAL_VOTES_ALLOWED && numVotesForTalk(mCurrentTalkId) > 0) {
    		getActivity().showDialog(RumpSessionActivity.VOTE_UNVOTE_CANCEL_DIALOG);
    	} else if (mVotes.size() >= TOTAL_VOTES_ALLOWED &&  numVotesForTalk(mCurrentTalkId) == 0) {
    		getActivity().showDialog(RumpSessionActivity.VOTES_AT_MAX_ALERT);
    	}      
    }
    
    /** Creates a HttpClient to connect via SSL/TLS to the voting server. */
    private HttpClient createHttpClient()
    {
        HttpParams params = new BasicHttpParams();
        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
        HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
        HttpProtocolParams.setUseExpectContinue(params, true);

        SchemeRegistry schReg = new SchemeRegistry();
        schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
        schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
        ClientConnectionManager conMgr = new ThreadSafeClientConnManager(params, schReg);

        return new DefaultHttpClient(conMgr, params);
    }
    

    /** This method is called when the user confirms a new vote. The last
     *  cached talk index is voted for. */
    public void handleCurrentVote(boolean add_vote) {
    	final Cursor cursor = (Cursor) mAdapter.getItem(mCurrentTalkId);
        final String rumpSessionTalkId;

        if (cursor != null) {
        	rumpSessionTalkId = cursor.getString(RumpSessionAdapter.RumpSessionQuery.RUMPSESSION_ORDER);
        } else {
        	rumpSessionTalkId = ScheduleContract.RumpSession.ALL_RUMPSESSION_TALKS_ID;
        }
        
        // Do the appropriate stuff for voting here
        int vote = cursor.getInt(RumpSessionAdapter.RumpSessionQuery.RUMPSESSION_VOTE);
        int order = cursor.getInt(RumpSessionAdapter.RumpSessionQuery.RUMPSESSION_ORDER);
        
        // Process vote action and update the local voting data
        if (add_vote) {
        	vote++;
        	addVote(order);
        } else {
        	vote--;
       		removeVote(order);
    	}
        	
        long updated = cursor.getLong(RumpSessionAdapter.RumpSessionQuery.UPDATED);
        String speaker = cursor.getString(RumpSessionAdapter.RumpSessionQuery.RUMPSESSION_SPEAKER);
        String title = cursor.getString(RumpSessionAdapter.RumpSessionQuery.RUMPSESSION_TITLE);
        
        // HACK!!!
        // Updating the vote column of the database
        // This could probably be done much nicer (with newUpdate?)
        try {
        	final Uri rumpSessionTalkUri = RumpSession.buildRumpSessionTalkUri(rumpSessionTalkId);
        	ContentResolver resolver = this.getActivity().getContentResolver();
        	String authority = ScheduleContract.CONTENT_AUTHORITY;
        	
        	final ArrayList<ContentProviderOperation> batch = Lists.newArrayList();
        	
        	batch.add(ContentProviderOperation.newDelete(rumpSessionTalkUri).build());
        	
        	final ContentProviderOperation.Builder builder = ContentProviderOperation
        			.newInsert(RumpSession.CONTENT_URI);
        	builder.withValue(SyncColumns.UPDATED, updated);
        	builder.withValue(RumpSession.RUMPSESSION_SPEAKER, speaker);
        	builder.withValue(RumpSession.RUMPSESSION_TITLE, title);
        	builder.withValue(RumpSession.RUMPSESSION_ORDER, order);
        	builder.withValue(RumpSession.RUMPSESSION_VOTE, vote);
        	batch.add(builder.build());
        	resolver.applyBatch(authority, batch);
        }
        // TODO: Only catch the appropriate exception and not all
        catch (Exception e) {
        	System.out.println(e);
        }
        
        // If we have three votes, report it to the server
        if (mVotes.size() == TOTAL_VOTES_ALLOWED) {
        	
            // Fire off HTTP POST to server to indicate vote
            // Create a new HttpClient and Post Header
            HttpClient httpClient = createHttpClient();
            HttpPost httppost = new HttpPost(RUMPSESSION_SERVER_URL);

            try {
                // Add voting data
                List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(4);                
                nameValuePairs.add(new BasicNameValuePair("v1", mVotes.get(0).toString()));
                nameValuePairs.add(new BasicNameValuePair("v2", mVotes.get(1).toString()));
                nameValuePairs.add(new BasicNameValuePair("v3", mVotes.get(2).toString()));
                nameValuePairs.add(new BasicNameValuePair("user", mUserPseudonym));
                httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

                // Execute HTTP Post Request
                // Note: In Android >= Honeycomb, networking in the main thread
                // is no longer allowed
                //HttpResponse response = httpClient.execute(httppost);
                
                // Execute HTTP Post Request in the background
                final HttpClient finalHttpClient = httpClient;
                final HttpPost finalHttpPost = httppost;
                new AsyncTask<Void, Void, String>() {
                	
                    @Override
                    protected String doInBackground(Void... voids) {
                    	String responseBody = null;
                    	try {
                    		ResponseHandler<String> responseHandler=new BasicResponseHandler();
                    		responseBody = finalHttpClient.execute(finalHttpPost, responseHandler);
                    	} catch (ClientProtocolException e) {
                    		responseBody = null;
                        } catch (IOException e) {
                        	responseBody = null;
                        }
                        return responseBody;
                    }
                    
                    @Override
                    protected void onPostExecute(String result) {
                    	if (result == null) {
                    		// Communication error occurred
                    		RumpSessionFragment.this.getActivity().showDialog(RumpSessionActivity.VOTE_COMMUNICATION_ERROR);
                    	} else if (result.indexOf("Error") > -1) {
                			// User pseudonym is wrong
                			RumpSessionFragment.this.getActivity().showDialog(RumpSessionActivity.VOTE_PSEUDONYM_UNKNOWN);
                		} else if (result.indexOf("Thank you for voting") > -1) {
                			// Vote was recorded successfully
                			RumpSessionFragment.this.getActivity().showDialog(RumpSessionActivity.VOTE_RECORDED_OK);
                		} else {
                			// Unknown server response
                			RumpSessionFragment.this.getActivity().showDialog(RumpSessionActivity.VOTE_UNKNOWN_SERVER_RESPONSE);
                		}
                    }
                    
                }.execute();
            
            } catch (UnsupportedEncodingException e) {
            	getActivity().showDialog(RumpSessionActivity.VOTE_COMMUNICATION_ERROR);
            }

        }
                
        // Update view (RumpSessionAdapter is notified once query is complete)
        // Start background query to load rump session talks
        final Intent intent = BaseActivity.fragmentArgumentsToIntent(getArguments());
        final Uri rumpSessionUri = intent.getData();
        mHandler = new NotifyingAsyncQueryHandler(getActivity().getContentResolver(), this);
        mHandler.startQuery(rumpSessionUri, RumpSessionAdapter.RumpSessionQuery.PROJECTION, null, null,
                ScheduleContract.RumpSession.DEFAULT_SORT);     	    	
    }
    
    /** Determines the number of votes already cast for a given certain rump
     *  session talk (based on the local data).
     */
    private int numVotesForTalk(int talk_id) {
    	if (mVotes == null) {
    		return 0;
    	} else {
    		int votes = 0;
    		Iterator<Integer> iter = mVotes.iterator();
    		while (iter.hasNext()) {
    			if (iter.next().intValue() == talk_id) {
    				votes++;
    			}
    		}
    		return votes;
    	}
    }
    
    /** Adds a vote to the local voting data. Performs some consistency
     *  checks before adding.
     */
    private void addVote(int talk_id) {
    	if (mVotes != null && mVotes.size() < TOTAL_VOTES_ALLOWED) {
    		mVotes.add(new Integer(talk_id));
    	}
    }
    
    /** Removes a vote to the local voting data. Performs some consistency
     *  checks before adding.
     */
    private void removeVote(int talk_id) {
    	if (mVotes != null) {
    		Iterator<Integer> iter = mVotes.iterator();
    		while (iter.hasNext()) {
    			if (iter.next().intValue() == talk_id) {
    				iter.remove();
    				break;
    			}
    		}
    	}
    }
    
    /** Sets the user pseudonym for voting to the given value. */
    public void setUserPseudonym(String user_pseudonym) {
    	mUserPseudonym = user_pseudonym;
    }

    
}
